﻿/***********************************************************************************************
COPYRIGHT 2008 Vijeth D

This file is part of Handwritten Character Recognition - NeuronDotNet Sample.
(Project Website : http://neurondotnet.freehostia.com)

NeuronDotNet is a free software. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.

NeuronDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with NeuronDotNet.
If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************************/

using System;
using System.Drawing;

namespace NeuronDotNet.Controls
{
    public class Line
    {
        private readonly Point start;
        private readonly Point end;

        private readonly double slope;
        private readonly double length;
        private readonly double lengthSquare;
        private readonly DiscreteSlope discreteSlope;

        public Point Start
        {
            get { return start; }
        }

        public Point End
        {
            get { return end; }
        }

        public double Slope
        {
            get { return slope; }
        }

        public double Length
        {
            get { return length; }
        }

        public DiscreteSlope DiscreteSlope
        {
            get { return discreteSlope; }
        }
        
        public Line(Point start, Point end)
        {
            this.start = start;
            this.end = end;

            double xDifference = end.X - start.X;
            double yDifference = end.Y - start.Y;

            lengthSquare = xDifference * xDifference + yDifference * yDifference;
            length = Math.Sqrt(lengthSquare);

            if (xDifference == 0)
            {
                slope = (yDifference < 0) ? Double.NegativeInfinity : Double.PositiveInfinity;
            }
            else
            {
                slope = yDifference / xDifference;
            }

            double positiveSlope = Math.Abs(slope);
            if (positiveSlope > 2.5)
            {
                discreteSlope = DiscreteSlope.Vertical;
            }
            else if (positiveSlope > 0.4)
            {
                discreteSlope = DiscreteSlope.Oblique;
            }
            else
            {
                discreteSlope = DiscreteSlope.Horizontal;
            }
        }

        public Line(Line line)
        {
            Helper.ValidateNotNull(line, "line");
            start = line.start;
            end = line.end;

            slope = line.slope;
            discreteSlope = line.discreteSlope;
            length = line.length;
            lengthSquare = line.lengthSquare;
        }

        public Line Reverse()
        {
            return new Line(end, start);
        }

        public static Line Clip(Rectangle clipBounds, Line line)
        {
            Point start = line.start;
            Point end = line.end;

            int lineCode = 0;

            if (end.Y < clipBounds.Top)
            {
                lineCode |= 8;
            }
            else if (end.Y > clipBounds.Bottom)
            {
                lineCode |= 4;
            }

            if (end.X > clipBounds.Right)
            {
                lineCode |= 2;
            }
            else if (end.X < clipBounds.Left)
            {
                lineCode |= 1;
            }

            if (start.Y < clipBounds.Top)
            {
                lineCode |= 128;
            }
            else if (start.Y > clipBounds.Bottom)
            {
                lineCode |= 64;
            }

            if (start.X > clipBounds.Right)
            {
                lineCode |= 32;
            }
            else if (start.X < clipBounds.Left)
            {
                lineCode |= 16;
            }

            // 9 8 A
            // 1 0 2
            // 5 4 6
            switch (lineCode)
            {
                case 0x00:
                    break;
                
                case 0x01:
                    ClipEndLeft(clipBounds, start, ref end);
                    break;

                case 0x02:
                    ClipEndRight(clipBounds, start, ref end);
                    break;

                case 0x04:
                    ClipEndBottom(clipBounds, start, ref end);
                    break;

                case 0x05:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    break;

                case 0x06:
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    break;

                case 0x08:
                    ClipEndTop(clipBounds, start, ref end);
                    break;

                case 0x09:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    break;

                case 0x0A:
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    break;

                case 0x10:
                    ClipStartLeft(clipBounds, ref start, end);
                    break;

                case 0x12:
                    ClipStartLeft(clipBounds, ref start, end);
                    ClipEndRight(clipBounds, start, ref end);
                    break;

                case 0x14:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipEndBottom(clipBounds, start, ref end);
                    break;

                case 0x16:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipEndBottom(clipBounds, start, ref end);
                    if (end.X > clipBounds.Right)
                    {
                        ClipEndRight(clipBounds, start, ref end);
                    }
                    break;

                case 0x18:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipEndTop(clipBounds, start, ref end);
                    break;

                case 0x1A:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipEndTop(clipBounds, start, ref end);
                    if (end.X > clipBounds.Right)
                    {
                        ClipEndRight(clipBounds, start, ref end);
                    }
                    break;

                // right
                case 0x20:
                    ClipStartRight(clipBounds, ref start, end);
                    break;

                case 0x21:
                    ClipStartRight(clipBounds, ref start, end);
                    ClipEndLeft(clipBounds, start, ref end);
                    break;

                case 0x24:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipEndBottom(clipBounds, start, ref end);
                    break;

                case 0x25:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipEndBottom(clipBounds, start, ref end);
                    if (end.X < clipBounds.Left)
                    {
                        ClipEndLeft(clipBounds, start, ref end);
                    }
                    break;

                case 0x28:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipEndTop(clipBounds, start, ref end);
                    break;

                case 0x29:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipEndTop(clipBounds, start, ref end);
                    if (end.X < clipBounds.Left)
                    {
                        ClipEndLeft(clipBounds, start, ref end);
                    }
                    break;

                // bottom
                case 0x40:
                    ClipStartBottom(clipBounds, ref start, end);
                    break;

                case 0x41:
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    break;

                case 0x42:
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    break;

                case 0x48:
                    ClipStartBottom(clipBounds, ref start, end);
                    ClipEndTop(clipBounds, start, ref end);
                    break;

                case 0x49:
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    break;

                case 0x4A:
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    break;

                // bottom-left
                case 0x50:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        ClipStartBottom(clipBounds, ref start, end);
                    }
                    break;

                case 0x52:
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        ClipStartLeft(clipBounds, ref start, end);
                    }
                    break;

                case 0x58:
                    ClipEndTop(clipBounds, start, ref end);
                    if (end.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        ClipStartLeft(clipBounds, ref start, end);
                    }
                    break;

                case 0x5A:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    if (start.Y > clipBounds.Bottom)
                    {
                        ClipStartBottom(clipBounds, ref start, end);
                    }
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    break;

                // bottom-right
                case 0x60:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        ClipStartBottom(clipBounds, ref start, end);
                    }
                    break;

                case 0x61:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipStartBottom(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        ClipStartRight(clipBounds, ref start, end);
                    }
                    break;

                case 0x68:
                    ClipEndTop(clipBounds, start, ref end);
                    if (end.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        ClipStartBottom(clipBounds, ref start, end);
                    }
                    break;

                case 0x69:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    if (end.Y < clipBounds.Top)
                    {
                        ClipEndTop(clipBounds, start, ref end);
                    }
                    if (start.Y > clipBounds.Bottom)
                    {
                        ClipStartBottom(clipBounds, ref start, end);
                    }
                    break;

                // top
                case 0x80:
                    ClipStartTop(clipBounds, ref start, end);
                    break;

                case 0x81:
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipEndLeft(clipBounds, start, ref end);
                    break;

                case 0x82:
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    break;

                case 0x84:
                    ClipStartTop(clipBounds, ref start, end);
                    ClipEndBottom(clipBounds, start, ref end);
                    break;

                case 0x85:
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    break;

                case 0x86:
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    break;

                // top-left
                case 0x90:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        ClipStartTop(clipBounds, ref start, end);
                    }
                    break;

                case 0x92:
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X < clipBounds.Left)
                    {
                        ClipStartLeft(clipBounds, ref start, end);
                    }
                    break;

                case 0x94:
                    ClipEndBottom(clipBounds, start, ref end);
                    if (end.X < clipBounds.Left)
                    {
                        return null;
                    }
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        ClipStartTop(clipBounds, ref start, end);
                    }
                    break;

                case 0x96:
                    ClipStartLeft(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    ClipEndRight(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    if (start.Y < clipBounds.Top) ClipStartTop(clipBounds, ref start, end);
                    if (end.Y > clipBounds.Bottom) ClipEndBottom(clipBounds, start, ref end);
                    break;

                // top-right
                case 0xA0:
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        ClipStartTop(clipBounds, ref start, end);
                    }
                    break;

                case 0xA1:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipStartTop(clipBounds, ref start, end);
                    if (start.X > clipBounds.Right)
                    {
                        ClipStartRight(clipBounds, ref start, end);
                    }
                    break;

                case 0xA4:
                    ClipEndBottom(clipBounds, start, ref end);
                    if (end.X > clipBounds.Right)
                    {
                        return null;
                    }
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y < clipBounds.Top)
                    {
                        ClipStartTop(clipBounds, ref start, end);
                    }
                    break;

                case 0xA5:
                    ClipEndLeft(clipBounds, start, ref end);
                    if (end.Y < clipBounds.Top)
                    {
                        return null;
                    }
                    ClipStartRight(clipBounds, ref start, end);
                    if (start.Y > clipBounds.Bottom)
                    {
                        return null;
                    }
                    if (end.Y > clipBounds.Bottom)
                    {
                        ClipEndBottom(clipBounds, start, ref end);
                    }
                    if (start.Y < clipBounds.Top)
                    {
                        ClipStartTop(clipBounds, ref start, end);
                    }
                    break;
                default:
                    return null;
            }
            return new Line(start, end);
        }

        private static void ClipStartTop(Rectangle bounds, ref Point start, Point end)
        {
            start.X += (end.X - start.X) * (bounds.Top - start.Y) / (end.Y - start.Y);
            start.Y = bounds.Top;
        }

        private static void ClipStartBottom(Rectangle bounds, ref Point start, Point end)
        {
            start.X += (end.X - start.X) * (bounds.Bottom - start.Y) / (end.Y - start.Y);
            start.Y = bounds.Bottom;
        }

        private static void ClipStartRight(Rectangle bounds, ref Point start, Point end)
        {
            start.Y += (end.Y - start.Y) * (bounds.Right - start.X) / (end.X - start.X);
            start.X = bounds.Right;
        }

        private static void ClipStartLeft(Rectangle bounds, ref Point start, Point end)
        {
            start.Y += (end.Y - start.Y) * (bounds.Left - start.X) / (end.X - start.X);
            start.X = bounds.Left;
        }

        private static void ClipEndTop(Rectangle bounds, Point start, ref Point end)
        {
            end.X += (end.X - start.X) * (bounds.Top - end.Y) / (end.Y - start.Y);
            end.Y = bounds.Top;
        }

        private static void ClipEndBottom(Rectangle bounds, Point start, ref Point end)
        {
            end.X += (end.X - start.X) * (bounds.Bottom - end.Y) / (end.Y - start.Y);
            end.Y = bounds.Bottom;
        }

        private static void ClipEndRight(Rectangle bounds, Point start, ref Point end)
        {
            end.Y += (end.Y - start.Y) * (bounds.Right - end.X) / (end.X - start.X);
            end.X = bounds.Right;
        }

        private static void ClipEndLeft(Rectangle bounds, Point start, ref Point end)
        {
            end.Y += (end.Y - start.Y) * (bounds.Left - end.X) / (end.X - start.X);
            end.X = bounds.Left;
        }

        private bool IsInside(Rectangle rect, Point point)
        {
            return rect.Left <= point.X && rect.Right >= point.X && rect.Top <= point.Y && rect.Bottom >= point.Y;
        }

        public bool IsInside(Rectangle rect)
        {
            return IsInside(rect, start) && IsInside(rect, end);
        }
    }
}
